﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Microsoft.Linq.Expressions;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Generation;

namespace JavascriptEngine.Runtime
{
    public class BinaryOperation : ExtensionBinaryOperationBinder
    {
        private Func<DynamicMetaObject, DynamicMetaObject, DynamicMetaObject> _handler;
        internal JavascriptBinder Binder { get; private set; }

        public BinaryOperation(Operators oper, JavascriptBinder binder)
            : base(oper.ToString())
        {
            switch (oper)
            {
                case Operators.Add:
                case Operators.Multiply:
                case Operators.Divide:
                case Operators.Subtract:
                case Operators.Mod:
                    _handler = EnsureComputable;
                    break;
            }
            Binder = binder;
        }

        private DynamicMetaObject EnsureComputable(DynamicMetaObject target, DynamicMetaObject arg)
        {
            if (target.LimitType == typeof(string) || arg.LimitType == typeof(string))
                return EnsureString(target, arg);
            else
                return EnsureNumeric(target, arg);
        }

        private DynamicMetaObject EnsureString(DynamicMetaObject target, DynamicMetaObject arg)
        {
            if (target.LimitType != typeof(string))
                target = Binder.ConvertTo(typeof(string), ConversionResultKind.ImplicitCast, target);

            return Binder.DoOperation(ExtensionOperation, new[] { target, arg });
        }

        private DynamicMetaObject EnsureNumeric(DynamicMetaObject target, DynamicMetaObject arg)
        {
            switch (Type.GetTypeCode(target.LimitType))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                    break;

                default:
                    target = Binder.ConvertTo(typeof(double), ConversionResultKind.ImplicitCast, target);
                    break;
            }

            switch (Type.GetTypeCode(arg.LimitType))
            {
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                    break;

                default:
                    arg = Binder.ConvertTo(typeof(double), ConversionResultKind.ImplicitCast, arg);
                    break;
            }
            
            DynamicMetaObject retValue =  Binder.DoOperation(ExtensionOperation, new[] { target, arg });
            retValue = Binder.ConvertTo(typeof(JavaScriptObject), ConversionResultKind.ImplicitCast, retValue);
            return retValue;
        }
        public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
        {
            return _handler(target, arg);
        }

        
    }
}
